package de.bht.fb6.cg1.exercise3.implement;

import java.lang.reflect.Array;
import java.util.Arrays;

import de.bht.fb6.cg1.exercise3.Matrix;
import de.bht.fb6.cg1.exercise3.Ring;

/**
 * implementation of a {@link Matrix}
 * 
 * @param <T> type of the values in the matrix
 */
@SuppressWarnings("serial")
public class MatrixImpl<T extends Number> extends Number implements Matrix<T> {

	/**
	 * stores the single values from the matrix as operable ring elements.
	 */
	private final Ring<T>[][] values;

	/**
	 * stores the value type. this is a need, to allocate new ring values.
	 */
	protected Class<? extends Ring<T>> ringClass;

	/**
	 * rows of the matrix.
	 */
	private final int rows;

	/**
	 * columns of the matrix.
	 */
	private final int columns;

	/**
	 * Creates a matrix to a given two-dimensional array.
	 * O(n*m)
	 * 
	 * @param type	Type of the values
	 * @param vals	Values of the matrix
	 */
	public MatrixImpl(final T[][] vals, final Class<? extends Ring<T>> rClass) {
		this.ringClass = rClass;
		rows = vals.length;
		columns = vals[0].length;
		values = newRingArray(rows, columns);
		for (int i = 0; i < rows; ++i) {
			for (int j = 0; j < columns; ++j) {
				values[i][j] = newRingValue(vals[i][j]);
			}
		}
	}

	/**
	 * Creates a matrix to a given two-dimensional array.
	 * O(n*m)
	 * 
	 * @param vals	Values of the matrix
	 */
	@SuppressWarnings("unchecked")
	public MatrixImpl(final Ring<T>[][] vals) {
		// the warning here is just java backward compability
		ringClass = (Class<? extends Ring<T>>) vals[0][0].getClass();
		rows = vals.length;
		columns = vals[0].length;
		values = newRingArray(rows, columns);
		for (int i = 0; i < rows; ++i) {
			for (int j = 0; j < columns; ++j) {
				values[i][j] = vals[i][j];
			}
		}
	}

	/**
	 * Copies a Matrix.
	 * O(n*m)
	 * 
	 * this is used to get a {@link MatrixImpl} from a matrix.
	 * 
	 * @param <R>	RingType of the Matrix
	 * @param vals	Matrix values
	 * @param type	Type of the values
	 */
	public MatrixImpl(final Matrix<T> matrix, final Class<? extends Ring<T>> rClass) {
		ringClass = rClass;
		rows = matrix.getRows();
		columns = matrix.getColumns();
		values = newRingArray(rows, columns);
		for (int i = 0; i < rows; ++i) {
			for (int j = 0; j < columns; ++j) {
				values[i][j] = newRingValue(matrix.get(i, j));
			}
		}
	}

	/**
	 * Copy Constructor for a MatrixImpl.
	 * O(n*m)
	 * 
	 * @param matrix	Matrix to be copied
	 */
	public MatrixImpl(final MatrixImpl<T> matrix) {
		ringClass = matrix.getRingClass();
		rows = matrix.getRows();
		columns = matrix.getColumns();
		values = newRingArray(rows, columns);
		for (int i = 0; i < rows; ++i) {
			for (int j = 0; j < columns; ++j) {
				values[i][j] = matrix.getRing(i, j);
			}
		}
	}

	/**
	 * returns the rows from the matrix
	 * O(1)
	 * 
	 * @return row count.
	 */
	public int getRows() {
		return rows;
	}

	/**
	 * returns the columns of the matrix
	 * O(1)
	 * 
	 * return column count
	 */
	public int getColumns() {
		return columns;
	}

	/**
	 * get the value for a specific row and column.
	 * O(1)
	 * 
	 * @param row	a given row
	 * @param col	a given column
	 * @return value on a given position
	 */
	public T get(final int row, final int col) {
		return values[row][col].get();
	}

	/**
	 * get the value from a specific entry as ring element.
	 * O(1)
	 * 
	 * @param row	a given row
	 * @param col	a given column
	 * @return ring element from a cell.
	 */
	protected Ring<T> getRing(final int row, final int col) {
		return values[row][col];
	}

	/**
	 * returns the matrix as a user friendly string.
	 * O(n*m)
	 * 
	 * @return string representation
	 */
	@Override
	public String toString() {
		final StringBuffer sb = new StringBuffer();
		sb.append('(');
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				sb.append(get(i,j));
				if (j < (columns-1)) {
					sb.append(' ');
				}
			}
			if (i < (rows-1)) {
				sb.append("; ");
			}
		}
		sb.append(')');
		return sb.toString();
	}

	/**
	 * Adds two matrices and returns the resulting matrix.
	 * O(n*m)
	 * 
	 * @param another matrix to add
	 * @result new resulting matrix
	 */
	@Override
	public MatrixImpl<T> add(final Matrix<T> matrix) {
		checkBoundaries(matrix);
		final Ring<T>[][] result = newRingArray(rows, columns);
		for (int i = 0; i < rows; ++i) {
			for (int j = 0; j < columns; ++j) {
				result[i][j] = getRing(i,j).add(newRingValue(matrix.get(i,j)));
			}
		}
		return new MatrixImpl<T>(result);
	}

	/**
	 * returns the transposed matrix of the current one.
	 * O(n*m)
	 * 
	 * @return The current matrix transposed.
	 */
	public MatrixImpl<T> getTransposed() {
		final Ring<T>[][] result = newRingArray(columns, rows);
		for (int i = 0; i < columns; i++) {
			for (int j = 0; j < rows; j++) {
				result[i][j] = values[j][i];
			}
		}
		return new MatrixImpl<T>(result);
	}

	/**
	 * Multiplies the current matrix with the given one.
	 * O(n*m*p)
	 * 
	 * @param matrix The matrix that will be used for multiplication. Must not be 'null'
	 * @return The result of the multiplication.
	 */
	@Override
	public MatrixImpl<T> mult(final Matrix<T> matrix) {
		if (getColumns() != matrix.getRows()) {
			throw new IllegalArgumentException("Boundary mismatch.");
		}
		final Ring<T>[][] result = newRingArray(getRows(), matrix.getColumns());
		for (int i = 0; i < getRows(); i++) {
			for (int j = 0; j < matrix.getColumns(); j++) {
				for (int k = 0; k < getColumns(); k++) {
					if (k == 0) {
						result[i][j] = values[i][k].times(newRingValue(matrix.get(k, j)));
					} else {
						result[i][j] = result[i][j].add(values[i][k].times(newRingValue(matrix.get(k, j))));
					}
				}
			}
		}
		return new MatrixImpl<T>(result);
	}

	/**
	 * Multiplies the current matrix with a given value and returns the result.
	 * O(n*m)
	 * 
	 * @param value The value with that the matrix should be multiplied.
	 * @return the resulting matrix
	 */
	@Override
	public MatrixImpl<T> mult(final T value) {
		final Ring<T>[][] result = newRingArray(rows, columns);
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				result[i][j] = values[i][j].times(newRingValue(value));
			}
		}
		return new MatrixImpl<T>(result);
	}

	/**
	 * Subtract a {@link Matrix} from this one.
	 * O(n*m)
	 * 
	 * @param matrix The matrix that should be subtracted to the current matrix. Must not be 'null'.
	 * @return The result of the subtraction.
	 */
	@Override
	public MatrixImpl<T> sub(final Matrix<T> matrix) {
		checkBoundaries(matrix);
		final Ring<T>[][] result = newRingArray(rows, columns);
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				result[i][j] = values[i][j].sub(newRingValue(matrix.get(i, j)));
			}
		}
		return new MatrixImpl<T>(result);
	}

	/**
	 * Check the boundaries from a given {@link Matrix} against the current one
	 * when the boundaries are different it'll throw a {@link IllegalArgumentException}
	 * O(1)
	 * 
	 * @param matrix The matrix to check with
	 */
	protected void checkBoundaries(final Matrix<T> matrix) {
		if (matrix.getColumns() != getColumns()) {
			throw new IllegalArgumentException("Matrix bounderies mismatch.");
		}
		if (matrix.getRows() != getRows()) {
			throw new IllegalArgumentException("Matrix bounderies mismatch.");
		}
	}

	/**
	 * Return the (i,j)-Minor for the given Matrix.
	 * The Parameters have to be in range, otherwise an IllegalArgumentException
	 * will arise.
	 * O(n*m)
	 * 
	 * @param row the row to be skipped;    0 < row < rows
	 * @param col the column to be skipped; 0 < col < columns
	 * @return the Minor
	 */
	public MatrixImpl<T> getMinor(final int row, final int col) {
		if (row < 0 || row >= rows || col < 0 || col >= columns) {
			throw new IllegalArgumentException("Out of Bounderies");
		}
		final Ring<T>[][] vals = newRingArray(rows-1, columns-1);
		int skip_row = 0;
		for (int i = 0; i < (rows-1); i++) {
			int skip_col = 0;
			for (int j = 0; j < (columns-1); j++) {
				if (i == row) {
					skip_row = 1;
				}
				if (j == col) {
					skip_col = 1;
				}
				vals[i][j] = values[i + skip_row][j + skip_col];
			}
		}
		return new MatrixImpl<T>(vals);
	}

	/**
	 * Calculate an absolute of the Matrix or Vector. We use the "Entrywise"
	 * 2-norm. This is the standard Norm for Vectors. (geometric length of these)
	 * O(n*m)
	 * 
	 * @return real number, representing the size of the Matrix/Vector
	 */
	public T absolute() {
		Ring<T> abs = getRing(0,0).times(getRing(0,0));
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				if (i != 0 || j != 0) {
					abs = abs.add(getRing(i,j).times(getRing(i,j)));
				}
			}
		}
		return abs.get();
	}

	/**
	 * get the absolute value of the matrix as double
	 * @return absolute double representation value
	 * @see MatrixImpl#absolute()
	 */
	@Override
	public double doubleValue() {
		return absolute().doubleValue();
	}

	/**
	 * get the absolute value of the matrix as float
	 * @return absolute float representation value
	 * @see MatrixImpl#absolute()
	 */
	@Override
	public float floatValue() {
		return absolute().floatValue();
	}

	/**
	 * get the absolute value of the matrix as int
	 * @return absolute int representation value
	 * @see MatrixImpl#absolute()
	 */
	@Override
	public int intValue() {
		return absolute().intValue();
	}

	/**
	 * get the absolute value of the matrix as long
	 * @return absolute long representation value
	 * @see MatrixImpl#absolute()
	 */
	@Override
	public long longValue() {
		return absolute().longValue();
	}

	/**
	 * get the ring class from the matrix
	 * @return ring class
	 */
	public Class<? extends Ring<T>> getRingClass() {
		return ringClass;
	}

	/**
	 * get the zero ring
	 * @return zero ring element
	 */
	protected Ring<T> RingZero() {
		Ring<T> zero;
		try {
			zero = ringClass.newInstance();
		} catch (final Exception e) {
			throw new IllegalStateException("Could not Instanciate Zero Ring Element");
		}
		return zero.zero();
	}

	/**
	 * get the one ring element
	 * @return one ring element
	 */
	protected Ring<T> RingOne() {
		Ring<T> one;
		try {
			one = ringClass.newInstance();
		} catch (final Exception e) {
			throw new IllegalStateException("Could not Instanciate Zero Ring Element");
		}
		return one.one();
	}

	/**
	 * get a new ring with a given value
	 * @param value value to set the ring to
	 * @return new ring
	 */
	protected Ring<T> newRingValue(final T value) {
		Ring<T> rNew;
		try {
			rNew = ringClass.newInstance();
		} catch (final Exception e) {
			throw new IllegalStateException("Could not allocate a new ring element");
		}
		rNew.set(value);
		return rNew;
	}

	/**
	 * get a new array from the ring type
	 * @param rows rows for the array
	 * @param columns columns for the array
	 * @return the created array
	 */
	@SuppressWarnings("unchecked")
	protected Ring<T>[][] newRingArray(final int rows, final int columns) {
		Ring<T>[][] values;
		// the warning here is just java backward compatibility
		values = (Ring<T>[][]) Array.newInstance(ringClass, rows, columns);
		return values;
	}

	/**
	 * Create a zero-filled matrix of given size.
	 * O(n*m)
	 * 
	 * @param rows rows of the new matirx
	 * @param columns columns of the new matrix
	 * @return zero filled matrix
	 */
	public Matrix<T> createZero(final int rows, final int columns) {
		final Ring<T>[][] values = newRingArray(rows, columns);
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < columns; j++) {
				values[i][j] = RingZero();
			}
		}
		return new MatrixImpl<T>(values);
	}

	/**
	 * Create an identity matrix of a given size
	 * O(n*m)
	 * 
	 * @param rows rowssize
	 * @param columns columnsize
	 * @return identity matrix
	 */
	public MatrixImpl<T> createId(final int rows, final int columns) {
		final Ring<T>[][] values = newRingArray(rows, columns);
		for (int i = 0; i < rows && i < columns; i++) {
			values[i][i] = RingOne();
		}
		return new MatrixImpl<T>(values);
	}

	/**
	 * calculates the hashcode of the matrix
	 * @return numeric hashcode
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(values);
		return result;
	}

	/**
	 * compares the matrix to another.
	 * @param obj obj to compare
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final MatrixImpl<T> other = (MatrixImpl<T>) obj;
		if (!Arrays.equals(values, other.values)) {
			return false;
		}
		return true;
	}
}
